﻿using System;
using System.Collections.Generic;
using System.Collections;
using Layers;
using System.Text;

namespace Gaming
{

    /// <summary>
    /// Interface to game state in part of cards on the table
    /// </summary>
    

    /// <summary>
    /// Cards-on-table enumerating method
    /// </summary>
    public enum TableEnumMethod
    {
        /// <summary>
        /// Enumerate all tables with no respect which player gets which cards.
        /// So, {flop}, {river}, {turn}, {player1}, {player2} sets receive all possible
        /// card combinations, but it is Unknown, who receive next unique combination,
        /// player1 or player2. The method is faster
        /// </summary>
        Unordered,

        /// <summary>
        /// Enumerate all tables with respect to which player gets which cards.
        /// So {player1},{player2} will get all possible combinations and {flop},{turn},{river} will get
        /// also all possible values. Method is slow
        /// </summary>
        Ordered
    }

    public class TableEventArgs : EventArgs
    {
        public TableEventArgs() {  }
    }

    public class CardGroup : CardSet
    {
        private CardSetList _allowed;
        //private CardTable _table;

        private CardEnumerator _enumerator;

        public CardGroup(Card[] cards, int start, int length, bool copy) : base(cards, start, length, copy) 
        {
            _allowed = new CardSetList(CardSet.All52);
        }

        public CardGroup(CardSet other) : base(other)
        {
            _allowed = new CardSetList(CardSet.All52);
        }

        public CardGroup(int size)
            : base(size)
        {
        }

        //public CardTable Table { get { return _table; } }

        public CardGroup Next { get { return _next as CardGroup; } set { _next = value; } }

        public CardEnumerator Enumerator{get{return _enumerator;}set{_enumerator=value;}}

        public CardSetList Allowed { get { return _allowed; } set { _allowed = value; } }

        public void SetKnown(CardSetList allowed)
        {
            Allowed = allowed;
            Enumerator = new FixedCardEnumerator(this);
        }

        public void SetRandom()
        {
            Enumerator = new CardEnumerator(this);
        }

        public override string ToString()
        {
            return base.ToString() + " " + Enumerator.GetType().Name;
        }

    }

        
    public delegate void CardEnumerationDone(CardGroup group, CardEnumerator enu);

    public class CardEnumerator 
    {
        public static int Random = 1;
        public static int KnownSet = 2;

        protected CardGroup _group;
        
        protected CardEnumerationDone _done;

        protected Card[] _allowedSet;
        protected CardMask _free;

        public CardEnumerator(CardGroup group):this()
        {
            Init(group);
        }

        public CardEnumerator()
        {
            //_free = new CardMask(CardSet.All52.Mask);
        }

        protected virtual void Init(CardGroup group)
        {
            _group = group;
            
            //CloneFrom(group);

            List<Card> allowed = new List<Card>(_group.Allowed);
            
            _allowedSet = new Card[allowed.Count];

            for (int i = 0; i < allowed.Count; i++)
                _allowedSet[i] = allowed[i];
        }

        public CardMask Free { get { return _free; } set { _free = value; } }

        public CardEnumerationDone EnumerationDone{ get { return _done; } set { _done = value; } }

        protected CardEnumerator nextEnum 
        { 
            get { 
                if(_group.Next!=null) 
                    return _group.Next.Enumerator;
                return null;
            } 
        }

        public void Enumerate()
        {
            Enumerate(0);
        }

        public virtual void Enumerate(int j)
        {
            int acount = _allowedSet.Length;
            Enumerate(j, 0, acount);
        }

        public virtual void Enumerate(int j, int istart, int icount)
        {
            // enumerate all in the group

            for (int i = istart; i < icount; i++)
            {
                Card card = _allowedSet[i];
                if (_free.Contains(card))
                {
                    //_gen.Use(jdest, ref card);
                    _free.Remove(card);
                    _group[j] = card;

                    if (j + 1 < _group._count) // still in this group
                        this.Enumerate(j + 1, i + 1, icount);
                    else // proceed to the next group
                        if (null != nextEnum)
                            nextEnum.Enumerate(0, i + 1, icount);
                        else
                            if (null != _done)
                                _done(_group, this);

                    _free.Add(card);
                    //_gen.FreeCards = _gen.FreeCards.Add(card);
                }
                else
                {
                }
            }
        }

        public void EnumerateCombinations()
        {
            EnumerateCombinations(0, 0, _allowedSet.Length);
        }

        public void EnumerateCombinations(int j, int istart, int icount)
        {
            for (int i = istart; i < icount; i++)
            {
                Card card = _allowedSet[i];
                _group[j] = card;
                if (j + 1 < _group._count) // still in this group
                    this.EnumerateCombinations(j + 1, i + 1, icount);
                else
                    if (null != _done)
                        _done(_group, this);
            }
        }
    }

    public class FixedCardEnumerator : CardEnumerator
    {
        public FixedCardEnumerator(CardGroup group)
        {
            Init(group);
        }

        public override void Enumerate(int j)
        {
            Enumerate(j, 0, 0);
        }

        public override void Enumerate(int j, int istart, int icount)
        {

            int fcount = _group.Allowed.Length;

            int jdest = j + _group._start;

            for (int i = 0; i < fcount; i++ )
            {
                CardSet cset = _group.Allowed[i];

                if (_free.Contains(cset.Mask))
                {
                    _free.Remove(cset);

                    _group.CopyFrom(cset);



                    if (null != nextEnum)
                        nextEnum.Enumerate(0);
                    else
                        if (null != _done)
                            _done(_group, this);

                    _free.Add(cset);
                }
                else
                {
                }
            }
        }
    }

    public abstract class CardTable : CardSet
    {
        /// <summary>
        /// codes of 2*playersCount+3+1+1 cards,
        /// standing for player HoleCards, Flop, Turn and River
        /// </summary>
        protected CardSet _deck;
        protected CardSetList _groups;

        protected CardTable(int length) : base(length)
        {
        }

        protected CardTable()
        {
        }

        public CardTable(CardSet cset)
            : base(cset.cards, 0, cset.Count, false)
        {
         
        }

        public CardGroup ReorderGroups()
        {
            CardGroup head = null;
            CardGroup tail = null;
            for (int i = 0; i < Groups.Length; i++)
            {
                if (null == head)
                    head = tail = Groups[i] as CardGroup;
                else
                {
                    CardGroup g = Groups[i] as CardGroup;
                    if (g.Enumerator is FixedCardEnumerator)
                    {
                        g.Next = head;
                        head = g;
                    }
                    else
                    {
                        tail.Next = g;
                        tail = g;
                    }
                }
            }
            return head;
        }

        public string DumpGroups(CardGroup first)
        {
            StringBuilder sb = new StringBuilder();
            while (first != null)
            {
                sb.Append("->");
                sb.Append(first.ToString());
                first = first.Next;
            }
            return sb.ToString();
        }


        public CardSet Deck { get { return _deck; } set { _deck = value; } }

        public CardSetList Groups { get { return _groups; } set { _groups = value; } }

        public string ToString(int count)
        {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i <= count; i++)
            {
                if (i > 0 && IndexStartsGroup(i))
                    sb.Append(" | ");

                sb.Append(_cards[i].ToString());
            }
            if (count < _cards.Length - 1)
                sb.Append("...");
            return sb.ToString();
        }

        

        public override string ToString()
        {
            return this.GetType().Name + ":"+StateString();
        }

        public virtual string StateString()
        {
            return ToString(_cards.Length - 1);
        }

        public virtual bool IndexStartsGroup(int i)
        {
            for (int g = 0; g < Groups.Length; g++)
                if (Groups[g]._start == i)
                    return true;
            return false;
        }

        public abstract int PlayerCount { get; }

        public abstract CardGroup HoleCards(int player);

        public abstract CardSet BoardCards { get;}

        public abstract CardGroup VisibleBoardCards { get;}

        public abstract CardGroup InvisibleBoardCards { get;}

        public abstract CardSet HandCards(int player);


    }

    public class Odds
    {
        public ulong win;
        public ulong tie;
        public ulong total;
        public double equity;

        public int Player;
        public int HandValue;

        public double Win { get { return (double)win / total; } }
        public double Tie { get { return (double)tie / total; } }
        public double Lose { get { return (double)(total - win - tie) / total; } }
        public double Equity { get { return (double)equity / total; } }
        public ulong Total { get { return total; } }

        public Odds(int player)
        {
            Player = player;
        }

        public override string ToString()
        {
            return string.Format("win ({0:F3}) tie ({1:F3}) total {2} equity {3:F3}", Win * 100, Tie * 100, Total, Equity * 100);
        }
    }

    /// <summary>
    /// Groups mapping
    /// groups 1.. PlayerCount for players' hole  cards. These could be (any) or (some range)
    /// group 0 for visible board cards
    /// group PlayerCount+1 for unvisible board cards
    /// </summary>
    public class HoldemTable : CardTable
    {
        private int _playersCount;

        /// <summary>
        /// Each player has initially 2 hole-cards. HoleCards is the zero-based list 
        /// of CardSets representing initial player two cards.
        /// </summary>
        /// 

        public override CardGroup HoleCards(int player) { return Groups[player + 1] as CardGroup; }

        public override CardSet BoardCards { get { return CardSet.Merge(VisibleBoardCards, InvisibleBoardCards); } }

        public override CardGroup VisibleBoardCards { get { return Groups[0] as CardGroup; } }

        public override CardGroup InvisibleBoardCards { get { return Groups[PlayerCount + 1] as CardGroup; } }

        public override CardSet HandCards(int player) { return CardSet.Merge(HoleCards(player), VisibleBoardCards, InvisibleBoardCards); }

        /// <summary>
        /// Number of players at the table
        /// </summary>
        public override int PlayerCount { get { return _playersCount; } }

        protected HoldemTable()
        {
            _playersCount = 0;
        }

        public static HoldemTable FromPlayersAndVisibles(int playersCount, int boardVisible)
        {
            HoldemTable tbl = new HoldemTable();

            tbl.Deck = CardSet.All52;

            tbl.Init(playersCount, boardVisible);

            return tbl;
        }

        private CardGroup AddGroup(int ig, ref int offset, int len, CardGroup prev)
        {
            CardGroup g = new CardGroup(_cards, offset, len, false);
            g.Enumerator = new CardEnumerator(g);
            Groups[ig] = g;
            offset += len;
            if (null != prev)
                prev.Next = g;
            return g;
        }

        private void Init(int playerCount, int boardVisible)
        {
            _playersCount = playerCount;

            Resize(PlayerCount * 2 + 5);

            Groups = new CardSetList(PlayerCount + 2);

            int offset = 0;
            CardGroup prev = null;

            int i = 1;

            while(i<=PlayerCount)
                prev = AddGroup(i++, ref offset, 2, prev);

            CardGroup visible = AddGroup(0, ref offset, boardVisible, null);
            visible.Next = Groups[1] as CardGroup; // link to first player

            CardGroup unvisible = AddGroup(i, ref offset, 5 - boardVisible, Groups[PlayerCount] as CardGroup);
        }

        public override string StateString()
        {
            return this.PlayerCount+" @ "+base.StateString();
        }
    }

    public class OddsCalculator
    {
        public HoldemTable Table;
        private TableGenerator _gen;
        private Odds[] _odds;
        private int[] _handValues;

        public Odds[] Odds { get { return _odds; } }

        public OddsCalculator(HoldemTable table, TableGenerator tgen)
        {
            SetTable(table);

            SetGenerator(tgen);
        }

        private void SetGenerator(TableGenerator gen)
        {
            _gen = gen;
            _gen.TableGenerated += new EventHandler<TableEventArgs>(Table_Generated);
        }

        void Table_Generated(object sender, TableEventArgs e)
        {
            Update(Table);
        }

        private void SetTable(HoldemTable table)
        {
            Table = table;
            
            _odds = new Odds[table.PlayerCount];
            for (int i = 0; i < _odds.Length; i++)
                _odds[i] = new Odds(i);

            _handValues = new int[table.PlayerCount];
        }

        public void Update(HoldemTable table)
        {
            int handmax = -1;
            for (int i = 0; i < table.PlayerCount; i++)
            {
                _handValues[i] = Hand.FromCards(table.HandCards(i)).Code;
                if (_handValues[i] > handmax)
                {
                    handmax = _handValues[i];
                }
            }

            int nwin = 0;
            for (int i = 0; i < table.PlayerCount; i++)
                if (_handValues[i] == handmax)
                    nwin++;
            
            double prize = 1.0 / nwin;
            
            for (int i = 0; i < table.PlayerCount; i++)
            {
                _odds[i].total++;
                if (_handValues[i] == handmax)
                {
                    if (nwin == 1)
                        _odds[i].win++;
                    else
                        _odds[i].tie++;
                    _odds[i].equity += prize;
                }
            }
        }
    }



    /// <summary>
    /// Enumerates possible tables
    /// </summary>
    public class TableGenerator 
    {
        #region Fields

        private CardTable _cardTable;

        internal Card[] _cards;

        internal CardMask _free;

        private ulong _iter;

        private bool _debug = false;

        private TableEnumMethod _method = TableEnumMethod.Ordered;

        private CardConstraint[] _constraints;

        
        private EventHandler<TableEventArgs> _tableGeneratedEvent;
        #endregion

        #region Events

        /// <summary>
        /// Event of the table has been generated
        /// </summary>
        public event EventHandler<TableEventArgs> TableGenerated
        {
            add { _tableGeneratedEvent += value; }
            remove { _tableGeneratedEvent -= value; }
        }
        #endregion

        #region Properties

        public CardTable Table { get { return _cardTable; } set { SetTable(value); } }

        public CardMask FreeCards { get { return _free; } set { _free = value; } }

        public CardSet Deck { get { return _cardTable.Deck; } }

        public void SetTable(CardTable table)
        {
            _cardTable = table;
            _free = new CardMask(table.Deck.Mask.Code);
        }

        /// <summary>
        /// Iterating constraints.
        /// </summary>
        public CardConstraint[] Constraints { get { return _constraints; } set { _constraints = value; } }
        #endregion

        #region Constructors
        public TableGenerator(CardTable table)
        {
            _constraints = new CardConstraint[0];
            SetTable(table);
        }

        #endregion

 
        #region Methods


        public void Enumerate()
        {
            _iter = 0;
            
            DebugEnumerate();

            FreeCards = new CardMask(CardSet.All52.Mask);

            CardGroup first = Table.ReorderGroups();

            for (int i = 0; i < Table.Groups.Length; i++)
            {
                CardGroup grp = (Table.Groups[i] as CardGroup);
                grp.Enumerator.Free = this._free;
                if (grp.Next == null)
                    grp.Enumerator.EnumerationDone = EnumDone;
            }
            
            _cards = _cardTable._cards;

            CardEnumerator enu = first.Enumerator;

            enu.Enumerate(0);

            _cards = null;
        }
        #endregion

        #region Internal

        protected void DebugEnumerate()
        {
            Console.WriteLine("Enumerating table "+_cardTable.ToString());
            if(null!=_constraints && _constraints.Length>0)
                foreach (CardConstraint cc in _constraints)
                    Console.WriteLine("Constraint: " + cc.ToString());
        }


        public ulong Iteration { get { return _iter; } }


        internal void Use(int j, ref Card card)
        {
            _iter++;
            _free._mask &= ~(1ul << card._code);
            _cards[j] = card;
        }

        internal void Use(int j, CardSet cset)
        {
            _free._mask &= ~cset._mask._mask;
            cset.CopyTo(_cards, j, cset._count);
        }

        #region Optimize
        internal void Free(int j, ref Card card)
        {
            _free._mask |= (1ul << card.Code);
        }

        internal void Free(int j, CardSet cset)
        {
            _free._mask |= cset._mask._mask;
        }

        internal  bool IsFree(ref Card card)
        {
            return 0!=(_free._mask & (1ul << card.Code));
        }

        internal bool IsFree(CardSet cset)
        {
            return cset._mask._mask == (_free._mask & cset._mask._mask);
        }
        #endregion

        internal void Debug(int maxReadyIndex)
        {
            if(_debug)
            {
                Console.Write("DBG {0} {1}: {2}", maxReadyIndex,_iter, _cardTable.ToString(maxReadyIndex));
                Console.Write(" {0}", ShouldIterate(maxReadyIndex) ? "INCL":"EXCL");
                Console.WriteLine();
            }
        }

        protected bool ShouldIterate(int maxReadyIndex)
        {
            if (null == _constraints)
                return true;
            int end = _constraints.Length;
            for (int i = 0; i < end; i++)
                {
                    if (!_constraints[i].Validate(this, maxReadyIndex))
                        return false;
                }
            return true;
        }

        internal void EnumDone(CardGroup g, CardEnumerator enu)
        {
            FireTableGenerated();
        }

        internal void FireTableGenerated()
        {
            if (_debug)
            {
                Debug(Table.Count - 1);
                Table.Verify();
            }
            if (null != _tableGeneratedEvent)
                _tableGeneratedEvent(this, new TableEventArgs());
        }
        #endregion
    }
}